﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.ComponentModel.Composition;

using NuMetaheuristics;
using NuMetaheuristics.Genotypes;
using NuMetaheuristics.Operators.Vector;
using NuMetaheuristics.Operators.Value;

namespace NuMetaheuristics.Contexts
{
    /// <summary>
    /// Looks for a vector which matches the given ideal vector. A smaller 
    /// fitness score is better.
    /// </summary>
    /// <typeparam name="G"></typeparam>
    [DataContract(Name = "VectorMatchOf{0}", 
        Namespace = "http://numetaheuristics.codeplex.com/schemas")]
    abstract public class VectorMatch<G> : IContext<G>
        where G : IGenotype
    {
        [DataMember]
        protected int _length;

        [DataMember]
        protected G _ideal;

        [DataMember]
        protected IUnaryOperator<G> _initializer;

        /// <summary>
        /// The default, no-argument constructor.
        /// </summary>
        public VectorMatch()
        {
        }

        public VectorMatch(int length, IUnaryOperator<G> initializer)
        {
            _length = length;
            _initializer = initializer;

            var vector = Produce(true, true, false);
            _ideal = vector;
        }

        abstract public G Produce(bool initialize, bool makeValid, bool evaluate);
        abstract public void Initialize(ref G candidate);
        abstract public void Evaluate(ref G candidate);
        abstract public bool IsValid(G candidate);
        abstract public void MakeValid(ref G candidate);
    }

    [DataContract(Namespace = "http://numetaheuristics.codeplex.com/schemas")]
    [Export(typeof(IContext<DoubleVectorGenotype>))]
    public class DoubleVectorMatch : VectorMatch<DoubleVectorGenotype>
    {
        [DataMember]
        private DoubleVectorBounding _bounding;

        /// <summary>
        /// The default, no-argument constructor.
        /// </summary>
        public DoubleVectorMatch()
        {
        }

        public DoubleVectorMatch(int length, double min, double max)
            : base(length, new DoubleVectorInitializer(new DoubleRandomInitializer(min, max)))
        {
            _bounding = new DoubleVectorBounding(min, max);
        }

        override public DoubleVectorGenotype Produce(bool initialize, bool makeValid, bool evaluate)
        {
            var candidate = new DoubleVectorGenotype(_length);

            if (initialize)
            {
                Initialize(ref candidate);
            }

            if (makeValid && !IsValid(candidate))
            {
                MakeValid(ref candidate);
            }

            if (evaluate)
            {
                Evaluate(ref candidate);
            }

            return candidate;
        }

        override public void Initialize(ref DoubleVectorGenotype candidate)
        {
            _initializer.Operate(ref candidate);
        }

        override public void Evaluate(ref DoubleVectorGenotype genome)
        {
            if (genome.Vector.Length != _ideal.Vector.Length)
                throw new ArgumentOutOfRangeException();

            double total = 0.0;

            for (int i = 0; i < genome.Vector.Length; i++)
            {
                total += Math.Abs(_ideal.Vector[i] - genome.Vector[i]);
            }

            genome.Fitness = new FitnessSmallerIsBetter(total);
        }

        override public bool IsValid(DoubleVectorGenotype candidate)
        {
            return _bounding.IsBounded(candidate);
        }

        override public void MakeValid(ref DoubleVectorGenotype candidate)
        {
            if (IsValid(candidate))
                return;

            _bounding.Operate(ref candidate);
        }
    }

    [DataContract(Namespace = "http://numetaheuristics.codeplex.com/schemas")]
    [Export(typeof(IContext<IntegerVectorGenotype>))]
    public class IntegerVectorMatch : VectorMatch<IntegerVectorGenotype>
    {
        [DataMember]
        private IntegerVectorBounding _bounding;

        /// <summary>
        /// The default, no-argument constructor.
        /// </summary>
        public IntegerVectorMatch()
        {
        }

        public IntegerVectorMatch(int length, int min, int max)
            : base(length, new IntegerVectorInitializer(new IntegerRandomInitializer(min, max)))
        {
            _bounding = new IntegerVectorBounding(min, max);
        }

        override public IntegerVectorGenotype Produce(bool initialize, bool makeValid, bool evaluate)
        {
            var candidate = new IntegerVectorGenotype(_length);

            if (initialize)
            {
                Initialize(ref candidate);
            }

            if (makeValid && !IsValid(candidate))
            {
                MakeValid(ref candidate);
            }

            if (evaluate)
            {
                Evaluate(ref candidate);
            }

            return candidate;
        }

        override public void Initialize(ref IntegerVectorGenotype candidate)
        {
            _initializer.Operate(ref candidate);
        }

        override public void Evaluate(ref IntegerVectorGenotype genome)
        {
            if (genome.Vector.Length != _ideal.Vector.Length)
                throw new ArgumentOutOfRangeException();

            double total = 0.0;

            for (int i = 0; i < genome.Vector.Length; i++)
            {
                total += Math.Abs(_ideal.Vector[i] - genome.Vector[i]);
            }

            genome.Fitness = new FitnessSmallerIsBetter(total);
        }


        override public bool IsValid(IntegerVectorGenotype candidate)
        {
            return _bounding.IsBounded(candidate);
        }

        override public void MakeValid(ref IntegerVectorGenotype candidate)
        {
            if (IsValid(candidate))
                return;

            _bounding.Operate(ref candidate);
        }
    }

    [DataContract(Namespace = "http://numetaheuristics.codeplex.com/schemas")]
    [Export(typeof(IContext<BooleanVectorGenotype>))]
    public class BooleanVectorMatch : VectorMatch<BooleanVectorGenotype>
    {
        /// <summary>
        /// The default, no-argument constructor.
        /// </summary>
        public BooleanVectorMatch()
        {
        }

        public BooleanVectorMatch(int length)
            : base(length, new BooleanVectorInitializer(new BooleanRandomInitializer()))
        {
        }


        override public BooleanVectorGenotype Produce(bool initialize, bool makeValid, bool evaluate)
        {
            var candidate = new BooleanVectorGenotype(_length);

            if (initialize)
            {
                Initialize(ref candidate);
            }

            if (makeValid && !IsValid(candidate))
            {
                MakeValid(ref candidate);
            }

            if (evaluate)
            {
                Evaluate(ref candidate);
            }

            return candidate;
        }

        override public void Initialize(ref BooleanVectorGenotype candidate)
        {
            _initializer.Operate(ref candidate);
        }

        override public void Evaluate(ref BooleanVectorGenotype genome)
        {
            if (genome.Vector.Length != _ideal.Vector.Length)
                throw new ArgumentOutOfRangeException();

            double total = 0.0;

            for (int i = 0; i < genome.Vector.Length; i++)
            {
                if (_ideal.Vector[i] != genome.Vector[i])
                {
                    total += 1.0;
                }
            }

            genome.Fitness = new FitnessSmallerIsBetter(total);
        }

        override public bool IsValid(BooleanVectorGenotype candidate)
        {
            return true;
        }

        override public void MakeValid(ref BooleanVectorGenotype candidate)
        {
        }
    }
}
